<script setup>
import { onMounted, reactive, defineComponent, computed, ref, nextTick } from "vue"
import copyw from "@/components/copy.vue"
import { message } from "ant-design-vue"
import easyjson from "@/components/easyjson.vue"
import easytxt from "@/components/easytxt.vue"

onMounted(() => {})

const resultRef = ref(null)

const state = reactive({
    cText: "",
    res: {
        diff: {
            missingInObj1: [],
            missingInObj2: [],
            differentValues: [],
        },
        str: "",
    },
    hasDiff: false,
})
const left_editor = ref(null)
const right_editor = ref(null)
function clear() {
    left_editor.value.setContent("")
    right_editor.value.setContent("")
}
//最原始版
function deepCompare(obj1, obj2, path = "") {
    const differences = {
        missingInObj1: [],
        missingInObj2: [],
        differentValues: [],
    }

    function compare(currentPath, o1, o2) {
        const keys1 = Object.keys(o1)
        const keys2 = Object.keys(o2)

        // Find missing keys in obj1
        keys2.forEach((key) => {
            if (!(key in o1)) {
                differences.missingInObj1.push(`${currentPath}${key}`)
            }
        })

        // Find missing keys in obj2
        keys1.forEach((key) => {
            if (!(key in o2)) {
                differences.missingInObj2.push(`${currentPath}${key}`)
            }
        })

        // Compare values for existing keys
        const allKeys = new Set([...keys1, ...keys2])
        allKeys.forEach((key) => {
            const newPath = currentPath ? `${currentPath}.${key}` : key
            if (o1[key] !== o2[key]) {
                if (Array.isArray(o1[key]) && Array.isArray(o2[key])) {
                    // Compare arrays
                    const arrayDiff = compareArrays(o1[key], o2[key], newPath)
                    differences.missingInObj1.push(...arrayDiff.missingInObj1)
                    differences.missingInObj2.push(...arrayDiff.missingInObj2)
                    differences.differentValues.push(...arrayDiff.differentValues)
                } else if (
                    typeof o1[key] === "object" &&
                    typeof o2[key] === "object" &&
                    !Array.isArray(o1[key]) &&
                    !Array.isArray(o2[key])
                ) {
                    // If both values are objects, recursively compare them
                    const nestedDifferences = deepCompare(o1[key], o2[key], newPath)
                    differences.missingInObj1.push(...nestedDifferences.missingInObj1)
                    differences.missingInObj2.push(...nestedDifferences.missingInObj2)
                    differences.differentValues.push(...nestedDifferences.differentValues)
                } else {
                    // If values are different, add to the differences
                    differences.differentValues.push({ path: newPath, left: o1[key], right: o2[key] })
                }
            }
        })
    }

    function compareArrays(arr1, arr2, path) {
        const arrayDifferences = {
            missingInObj1: [],
            missingInObj2: [],
            differentValues: [],
        }

        const len1 = arr1.length
        const len2 = arr2.length
        const maxLen = Math.max(len1, len2)

        for (let i = 0; i < maxLen; i++) {
            const newPath = `${path}[${i}]`
            if (i >= len1) {
                arrayDifferences.missingInObj1.push(newPath)
            } else if (i >= len2) {
                arrayDifferences.missingInObj2.push(newPath)
            } else if (arr1[i] !== arr2[i]) {
                if (
                    typeof arr1[i] === "object" &&
                    typeof arr2[i] === "object" &&
                    !Array.isArray(arr1[i]) &&
                    !Array.isArray(arr2[i])
                ) {
                    // If both values are objects, recursively compare them
                    const nestedDifferences = deepCompare(arr1[i], arr2[i], newPath)
                    differences.missingInObj1.push(...nestedDifferences.missingInObj1)
                    differences.missingInObj2.push(...nestedDifferences.missingInObj2)
                    differences.differentValues.push(...nestedDifferences.differentValues)
                } else {
                    // If values are different, add to the differences
                    arrayDifferences.differentValues.push({ path: newPath, left: arr1[i], right: arr2[i] })
                }
            }
        }

        return arrayDifferences
    }

    compare(path, obj1, obj2)

    return differences
}
//原始版
function deepCompareAndFormat2(obj1, obj2) {
    let result = " {\n"
    const differences = {
        missingInObj1: [],
        missingInObj2: [],
        differentValues: [],
    }

    function stringify(value) {
        return JSON.stringify(value, null, 2)
    }

    function compare(currentPath, o1, o2, indent) {
        const indentSpaces = " ".repeat(indent)
        if (Array.isArray(o1) && Array.isArray(o2)) {
            const maxLength = Math.max(o1.length, o2.length)
            for (let i = 0; i < maxLength; i++) {
                if (o1[i] !== undefined && o2[i] !== undefined && o1[i] !== o2[i]) {
                    if (typeof o1[i] === "object" && typeof o2[i] === "object") {
                        result += `  ${indentSpaces}{\n`
                        compare(`${currentPath}[${i}]`, o1[i], o2[i], indent + 2)
                        result += `  ${indentSpaces}},\n`
                    } else {
                        differences.differentValues.push({ path: `${currentPath}[${i}]`, left: o1[i], right: o2[i] })
                        result += `> ${indentSpaces}${stringify(o1[i])},\n< ${indentSpaces}${stringify(o2[i])},\n`
                    }
                } else if (o1[i] !== undefined && o2[i] === undefined) {
                    differences.missingInObj2.push({ path: `${currentPath}[${i}]`, value: o1[i] })
                    result += `- ${indentSpaces}${stringify(o1[i])},\n`
                } else if (o1[i] === undefined && o2[i] !== undefined) {
                    differences.missingInObj1.push({ path: `${currentPath}[${i}]`, value: o2[i] })
                    result += `+ ${indentSpaces}${stringify(o2[i])},\n`
                } else {
                    result += `  ${indentSpaces}${stringify(o2[i])},\n`
                }
            }
        } else {
            const keys = new Map()
            Object.keys(o1).forEach((key) => keys.set(key, true))
            Object.keys(o2).forEach((key) => keys.set(key, true))

            keys.forEach((_, key) => {
                const newPath = currentPath ? `${currentPath}.${key}` : key
                if (!o1.hasOwnProperty(key)) {
                    differences.missingInObj1.push({ path: newPath, key: key, value: o2[key] })
                    result += `+ ${indentSpaces}"${key}": ${stringify(o2[key])},\n`
                } else if (!o2.hasOwnProperty(key)) {
                    differences.missingInObj2.push({ path: newPath, key: key, value: o1[key] })
                    result += `- ${indentSpaces}"${key}": ${stringify(o1[key])},\n`
                } else if (Array.isArray(o1[key]) && Array.isArray(o2[key])) {
                    result += `  ${indentSpaces}"${key}": [\n`
                    compare(newPath, o1[key], o2[key], indent + 2)
                    result += `  ${indentSpaces}],\n`
                } else if (typeof o1[key] === "object" && typeof o2[key] === "object") {
                    result += `  ${indentSpaces}"${key}": {\n`
                    compare(newPath, o1[key], o2[key], indent + 2)
                    result += `${indentSpaces}  },\n`
                } else if (stringify(o1[key]) !== stringify(o2[key])) {
                    differences.differentValues.push({ path: newPath, left: o1[key], right: o2[key] })
                    result += `> ${indentSpaces}"${key}": ${stringify(
                        o1[key],
                    )},\n< ${indentSpaces}"${key}": ${stringify(o2[key])},\n`
                } else {
                    result += `  ${indentSpaces}"${key}": ${stringify(o2[key])},\n`
                }
            })
        }
    }

    compare("", obj1, obj2, 2)

    result = result.slice(0, -2) + "\n }" // Remove the last comma and close the object

    return { str: result, diff: differences }
}
//简易优化版
function deepCompareAndFormat(obj1, obj2) {
    let result = " {\n"
    const differences = {
        missingInObj1: [],
        missingInObj2: [],
        differentValues: [],
    }

    function stringify(value) {
        return JSON.stringify(value, null, 2)
    }

    function compare(currentPath, o1, o2, indent) {
        const indentSpaces = " ".repeat(indent)
        if (Array.isArray(o1) && Array.isArray(o2)) {
            const maxLength = Math.max(o1.length, o2.length)
            for (let i = 0; i < maxLength; i++) {
                const newPath = `${currentPath}[${i}]`
                if (o1[i] !== undefined && o2[i] !== undefined) {
                    if (typeof o1[i] === "object" && typeof o2[i] === "object") {
                        result += `${indentSpaces}{\n`
                        compare(newPath, o1[i], o2[i], indent + 2)
                        result += `${indentSpaces}},\n`
                    } else if (o1[i] !== o2[i]) {
                        differences.differentValues.push({ path: newPath, left: o1[i], right: o2[i] })
                        result += `> ${indentSpaces}${stringify(o1[i])},\n< ${indentSpaces}${stringify(o2[i])},\n`
                    } else {
                        result += `${indentSpaces}${stringify(o2[i])},\n`
                    }
                } else if (o1[i] !== undefined) {
                    differences.missingInObj2.push({ path: newPath, value: o1[i] })
                    result += `- ${indentSpaces}${stringify(o1[i])},\n`
                } else {
                    differences.missingInObj1.push({ path: newPath, value: o2[i] })
                    result += `+ ${indentSpaces}${stringify(o2[i])},\n`
                }
            }
        } else {
            const keys = new Set([...Object.keys(o1), ...Object.keys(o2)])
            keys.forEach((key) => {
                const newPath = currentPath ? `${currentPath}.${key}` : key
                if (!o1.hasOwnProperty(key)) {
                    differences.missingInObj1.push({ path: newPath, key, value: o2[key] })
                    result += `+ ${indentSpaces}"${key}": ${stringify(o2[key])},\n`
                } else if (!o2.hasOwnProperty(key)) {
                    differences.missingInObj2.push({ path: newPath, key, value: o1[key] })
                    result += `- ${indentSpaces}"${key}": ${stringify(o1[key])},\n`
                } else if (Array.isArray(o1[key]) && Array.isArray(o2[key])) {
                    result += `${indentSpaces}"${key}": [\n`
                    compare(newPath, o1[key], o2[key], indent + 2)
                    result += `${indentSpaces}],\n`
                } else if (typeof o1[key] === "object" && typeof o2[key] === "object") {
                    result += `${indentSpaces}"${key}": {\n`
                    compare(newPath, o1[key], o2[key], indent + 2)
                    result += `${indentSpaces}},\n`
                } else if (stringify(o1[key]) !== stringify(o2[key])) {
                    differences.differentValues.push({ path: newPath, left: o1[key], right: o2[key] })
                    result += `> ${indentSpaces}"${key}": ${stringify(
                        o1[key],
                    )},\n< ${indentSpaces}"${key}": ${stringify(o2[key])},\n`
                } else {
                    result += `${indentSpaces}"${key}": ${stringify(o2[key])},\n`
                }
            })
        }
    }

    compare("", obj1, obj2, 2)

    result = result.slice(0, -2) + "\n }" // Remove the last comma and close the object

    return { str: result, diff: differences }
}

function compare() {
    const t1 = left_editor.value.getContent() || {}
    const t2 = right_editor.value.getContent() || {}
    if (!t1 || !t1.status) {
        message.error("左侧JSON格式错误")
        return
    }
    if (!t2 || !t2.status) {
        message.error("右侧JSON格式错误")
        return
    }
    const l1 = t1.data
    const l2 = t2.data

    const co = { red: [], green: [], yellow: [] }
    state.res = deepCompareAndFormat(l1, l2)
    console.log(state.res)
    if (
        state.res.diff.differentValues.length == 0 &&
        state.res.diff.missingInObj1.length == 0 &&
        state.res.diff.missingInObj2.length == 0
    ) {
        state.hasDiff = false
        message.success("两个JSON相同")
        return
    }
    state.hasDiff = true
    let ss = state.res.str.split("\n")
    for (var i = 0; i < ss.length; i++) {
        if (ss[i].startsWith("-")) {
            co.red.push(i + 1)
        } else if (ss[i].startsWith("+")) {
            co.green.push(i + 1)
        } else if (ss[i].startsWith(">") || ss[i].startsWith("<")) {
            co.yellow.push(i + 1)
        }
    }
    nextTick(() => {
        try {
            resultRef.value.setColor(co)
            resultRef.value.setContent(state.res.str)
        } catch (error) {}
    })

    // state.cText = JSON.stringify(res.diff, null, 4)
    // console.log(res.str)

    // left_editor.value.setColor(data1)
    // right_editor.value.setColor(data2)
}
</script>
<template>
    <div class="k">
        <div class="content">
            <h1 class="f_row" style="justify-content: flex-start">
                简易JSON比较<copyw title="简易JSON比较" ckey="jsonc"></copyw>
            </h1>
            <div class="f_row" style="gap: 12px; justify-content: flex-start; margin-bottom: 8px">
                <a-button type="primary" size="small" @click="compare">比较</a-button>
                <a-button @click="clear" size="small">清空</a-button>
            </div>
            <div style="height: calc(100vh - 160px); gap: 20px" class="f_row">
                <easyjson style="flex: 1" ref="left_editor"></easyjson>
                <easyjson style="flex: 1" ref="right_editor"></easyjson>
            </div>
            <h3 v-if="state.hasDiff" style="margin-top: 20px">比较整体结果</h3>
            <div v-if="state.hasDiff"></div>
            <easytxt
                :func="{ tags: true }"
                :disabled="true"
                ref="resultRef"
                v-if="state.hasDiff"
                style="height: 600px"
            ></easytxt>
            <h4
                v-if="state.hasDiff && state.res.diff.missingInObj1 && state.res.diff.missingInObj1.length > 0"
                style="margin-top: 20px"
            >
                右侧缺失值
            </h4>
            <div
                style="width: 100%; border: 1px solid var(--Neutral-6); padding: 8px 12px"
                class="auto-wrap"
                v-if="state.hasDiff && state.res.diff.missingInObj1 && state.res.diff.missingInObj1.length > 0"
            >
                <div v-for="v in state.res.diff.missingInObj1">{{ `${v.path}：${JSON.stringify(v.value)}` }}</div>
            </div>
            <h4
                v-if="state.hasDiff && state.res.diff.missingInObj2 && state.res.diff.missingInObj2.length > 0"
                style="margin-top: 20px"
            >
                左侧缺失值
            </h4>
            <div
                style="width: 100%; border: 1px solid var(--Neutral-6); padding: 8px 12px"
                class="auto-wrap"
                v-if="state.hasDiff && state.res.diff.missingInObj2 && state.res.diff.missingInObj2.length > 0"
            >
                <div v-for="v in state.res.diff.missingInObj2">{{ `${v.path}：${JSON.stringify(v.value)}` }}</div>
            </div>
            <h4
                v-if="state.hasDiff && state.res.diff.differentValues && state.res.diff.differentValues.length > 0"
                style="margin-top: 20px"
            >
                差异值
            </h4>
            <div
                style="width: 100%; border: 1px solid var(--Neutral-6); padding: 8px 12px"
                class="auto-wrap"
                v-if="state.hasDiff && state.res.diff.differentValues && state.res.diff.differentValues.length > 0"
            >
                <div v-for="v in state.res.diff.differentValues">
                    {{ `${v.path},左值：${JSON.stringify(v.left)} ,右值：${JSON.stringify(v.right)}` }}
                </div>
            </div>
        </div>
    </div>
</template>
<style scoped>
.content {
    max-width: 1280px;
    margin: 0 auto;
    padding: 32px;
    gap: 24px;
    align-items: flex-start;

    min-height: calc(100vh - 66px);
    justify-content: flex-start;
    position: relative;
    border-radius: 8px;
}

:where(.k) {
    height: 100%;
    background-color: var(--page-bg-color);
    padding: 16px;
}

h1,
h3,
h4 {
    color: var(--character-85);
    margin-bottom: 16px;
}

h1 {
    font-size: 24px;
}

h3 {
    font-size: 18px;
    color: var(--character-75);
}

h4 {
    font-size: 16px;
    color: var(--character-75);
}

.auto-wrap {
    background-color: var(--Neutral-11);
    border: 1px solid var(--Neutral-10) !important;
    color: var(--character-75);
    border-radius: 8px;
}

:deep(.ant-button) {
    background-color: var(--element-bg-color);
    border-color: var(--Neutral-9);
    color: var(--character-85);
}

:deep(.ant-button-primary) {
    background-color: var(--Geek-Blue-5);
    border-color: var(--Geek-Blue-5);
    color: var(--character-100);
}

:deep(.ant-button:hover) {
    border-color: var(--Geek-Blue-4);
    color: var(--Geek-Blue-4);
}

:deep(.ant-button-primary:hover) {
    background-color: var(--Geek-Blue-4);
    border-color: var(--Geek-Blue-4);
    color: var(--character-100);
}
</style>
<style>
.jsoneditor-menu {
    background-color: var(--page-bg-color) !important;
    border-bottom: 1px solid var(--Neutral-9) !important;
}
.jsoneditor {
    border: thin solid var(--Neutral-12) !important;
}
.ace_content,
.jsoneditor-text,
.jsoneditor-outer,
.jsoneditor-tree,
.jsoneditor-frame {
    background-color: var(--element-bg-color) !important;
    color: var(--character-85) !important;
}
.ace_active-line {
    background-color: var(--element-bg-color) !important;
    color: var(--character-85) !important;
    border-top: none !important;
}
.ace_variable {
    color: var(--character-75) !important;
}
.ace_cursor {
    border-left: 2px solid var(--character-85) !important;
}
.ace-jsoneditor .ace_gutter,
.jsoneditor-statusbar {
    background-color: var(--element-bg-color) !important;
    color: var(--character-60) !important;
    border-top: 1px solid var(--Neutral-9) !important;
}
.ace-jsoneditor .ace_gutter-active-line {
    background-color: var(--Neutral-9) !important;
    color: var(--character-85) !important;
}
</style>
